package it.antonio.ilpizze.db.core;

import it.antonio.ilpizze.db.core.fs.BtreeIndexFile;
import it.antonio.ilpizze.db.core.fs.HashIndexFile;
import it.antonio.ilpizze.db.core.fs.SecurityFile;
import it.antonio.ilpizze.db.core.fs.TransactionFile;
import it.antonio.ilpizze.db.exceptions.DbLockedException;
import it.antonio.ilpizze.db.io.IOFile;
import it.antonio.ilpizze.func.F;
import it.antonio.ilpizze.func.Mapper;

import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DbFileSystem {
	private File directory;
	
	private IOFile lockFile;
	
	private IOFile recordFile;
	
	private IOFile securityFile;
	
	private List<BtreeIndexFile> btreeIndexFiles = new ArrayList<BtreeIndexFile>();
	private List<HashIndexFile> hashIndexFiles = new ArrayList<HashIndexFile>();
	
	private Map<Integer, TransactionFile> openTransactions = new HashMap<Integer, TransactionFile>();
	private Map<Integer, TransactionFile> reusableTransactions = new HashMap<Integer, TransactionFile>();
	
	public DbFileSystem(File directory) {
		this.directory = directory;
	}
	
	public void lockDb(){
		if(lockFile == null){
			lockFile = IOFile.mmap(new File(directory, "lock.dat") , 1000000);
			lockFile.open();
			
		}
		if(lockFile.lock(1500) == false){
			throw new DbLockedException();
		}
	}
	
	public void unlockDb(){
		lockFile.unlock();
		lockFile.close();
		
	}
	public File dbDirectory() {
		return directory;
	}

	public IOFile openRecordFile(){
		recordFile = IOFile.mmap(new File(directory, "records.dat") , 1000000);
		recordFile.open();
		
		return recordFile;
	}
	
	public SecurityFile openSecurityFile(){
		securityFile = IOFile.mmap(new File(directory, "security.dat"), 1000000);
		securityFile.open();
		
		return new SecurityFile(securityFile);
	}
	
	public TransactionFile openTransactionLogFile(Integer id){
		
		TransactionFile transactionLog = reusableTransactions.remove(id);
		if(transactionLog == null){
			transactionLog = new TransactionFile(id, IOFile.mmap(new File(directory, "transactionLog_" + id + ".dat"), 10000));
			transactionLog.open();
		}
		openTransactions.put(id, transactionLog);
		
		transactionLog.activate();
		return transactionLog;
	}
	
	public void disposeTransactionLog(TransactionFile transactionLog) {
		Integer id = transactionLog.id();
		openTransactions.remove(id);
		reusableTransactions.put(id, transactionLog);
		transactionLog.dectivate();
	}

	public List<TransactionFile> openRecoverTransactions() {
		File[] transactionFiles = directory.listFiles(new FilenameFilter() {
			
			@Override
			public boolean accept(File dir, String name) {
				return name.startsWith("transactionLog");
			}
		});
		
		List<TransactionFile> recovers = new ArrayList<TransactionFile>();
		for(File file: transactionFiles){
			String fileNameString = file.getName();
			Integer id =  Integer.valueOf(fileNameString.substring(fileNameString.indexOf("transactionLog_") + 15, fileNameString.indexOf(".")));
			
			TransactionFile transaction = new TransactionFile(id, IOFile.mmap(file, 1000000));
			transaction.open();
			recovers.add(transaction);
		}
		return recovers;
	}

	
	public BtreeIndexFile openBtreeIndexFile(String name) {
		BtreeIndexFile btreeFile = new BtreeIndexFile(IOFile.mmap(new File(directory, "btree_index_" + name + ".dat"),10000));
		btreeFile.open();
		
		btreeIndexFiles.add(btreeFile);
		
		return btreeFile;
	}
	public HashIndexFile openHashIndexFile(String name) {
		HashIndexFile hashFile = new HashIndexFile(IOFile.mmap(new File(directory, "hash_index_" + name + ".dat"),10000));
		hashFile.open();
		
		hashIndexFiles.add(hashFile);
		
		return hashFile;
	}
	
	public void closeAllFiles() {
		if(securityFile != null){
			securityFile.close();
		}
		
		for(IOFile indexFile: btreeIndexFiles){
			indexFile.close();
		}
		for(IOFile indexFile: hashIndexFiles){
			indexFile.close();
		}
		if(recordFile != null){
			recordFile.close();
		}
		
		
		for(IOFile txFile: reusableTransactions.values()){
			txFile.close();
			while(txFile.delete());
		}
		
		
	}
	public File backupDirectory() {
		File backups = new File(directory, "backups");
		if(!backups.exists()) while(!backups.mkdir());
		return backups;
	}
	
	public File[] filesToBackup() {
		return directory.listFiles(new FilenameFilter() {
			
			@Override
			public boolean accept(File dir, String name) {
				if(name.equals("records.dat")) return true;
				if(name.equals("security.dat")) return true;
				if(name.equals("config.dat")) return true;
				if(name.startsWith("btree_index_")) return true;
				if(name.startsWith("hash_index_")) return true;
				return false;
			}
		});
	}

	public List<String> toOpenBtreeIndexes() {
		return F.map(Arrays.asList(directory.listFiles(new FilenameFilter() {
			
			@Override
			public boolean accept(File dir, String name) {
				if(name.startsWith("btree_index_")) return true;
				return false;
			}
		})), new Mapper<File, String>() {

			@Override
			public String map(File in) {
				String fileNameString = in.getName();
				return fileNameString.substring(fileNameString.indexOf("btree_index_")  + 12, fileNameString.indexOf("."));
			}
		});
	}
	
	public List<String> toOpenHashIndexes() {
		return F.map(Arrays.asList(directory.listFiles(new FilenameFilter() {
			
			@Override
			public boolean accept(File dir, String name) {
				if(name.startsWith("hash_index_")) return true;
				return false;
			}
		})),new Mapper<File, String>() {

			@Override
			public String map(File in) {
				String fileNameString = in.getName();
				return fileNameString.substring(fileNameString.indexOf("hash_index_") + 11, fileNameString.lastIndexOf("."));
			}
		});
	}

	public Long calculateSize() {
		return folderSize(directory);
	}
	
	private long folderSize(File directory) {
	    long length = 0;
	    for (File file : directory.listFiles()) {
	        if (file.isFile())
	            length += file.length();
	        else
	            length += folderSize(file);
	    }
	    return length;
	}
	public List<BtreeIndexFile> btreeIndexFiles() {
		return btreeIndexFiles;
	}
	public List<HashIndexFile> hashIndexFiles() {
		return hashIndexFiles;
	}
}
